#include "stdafx.h"
#include "TiffMerger.h"
#include "TiffLogger.h"
#include "resource.h"
#include "MFCTiffMergerDlg.h"
#include "TIFFMergeTab.h"
#include <list>
#include <locale.h>

typedef std::pair<CString, TIFF*> tiffFile;

bool alphabetOrder(const CString left, const CString right)
{
	if (left.CompareNoCase(right) <= 0)
		return true;
	return false;
}

void MergeTiff(const CString& parentPath)
{
	if (parentPath.IsEmpty()) {
		return ;
	}

	CMFCTiffMergerDlg* dlg = (CMFCTiffMergerDlg*)AfxGetApp()->GetMainWnd();
	TIFFMergeTab &m_page1 = dlg->m_page1;

    CButton* deleteOrigin = (CButton*)(m_page1.GetDlgItem(IDC_CHECK1));
	CFileFind finder;
	CString strWildCard = parentPath;
    if (strWildCard.Right(1) != _T("\\")) {
        strWildCard += _T("\\");
    }
    strWildCard += _T("*");

	if (finder.FindFile(strWildCard)) {
		BOOL bWorking = TRUE;
		while (bWorking) {			
			bWorking = finder.FindNextFileW();
			if (finder.IsDots())
				continue;

			if (finder.IsDirectory()) {
                // If it's the directory, instead of its subdirectories,
                // that contains tiff or jpeg files, then begin merge.
                if (containTiffOrJpegDirect(finder.GetFilePath())) {
				    DoMerge(finder.GetFilePath());
                    // Delete this directory if required
                    if (deleteOrigin->GetCheck()) {
                        RecursiveRemoveDirectory(finder.GetFilePath());
                    }
                } else {
                    MergeTiff(finder.GetFilePath());
                }
			}            
		}
		finder.Close();
	}
}

void DoMerge(const CString& szPath)
{
    // Assume that the szPath's last character is not '\'
	CString mergedFileName = szPath + _T(".tif");
	CString wildCard = szPath +_T("\\*");
	CMFCTiffMergerDlg* dlg = (CMFCTiffMergerDlg*)AfxGetApp()->GetMainWnd();
	TIFFMergeTab &m_page1 = dlg->m_page1;
	CProgressCtrl *mergeProgress  = (CProgressCtrl*)m_page1.GetDlgItem(IDC_PROGRESS1);
    CListBox* mergedFileNames = (CListBox*)m_page1.GetDlgItem(IDC_LIST1);
	std::list<CString> fileNameArray;
	
	TIFF *output;

	CFileFind finder;
	if (finder.FindFile(wildCard)) {
		BOOL bWorking = TRUE;
		while (bWorking) {
			bWorking = finder.FindNextFileW();

			if (finder.IsDots() || finder.IsDirectory())
				continue;

			CString alternative = finder.GetFilePath();
            if (IsTiffFile(finder.GetFileName()) ||
                IsJpegFile(finder.GetFileName())) {
			    fileNameArray.push_back(alternative);
            }
		}
        finder.Close();
	}

	fileNameArray.sort(alphabetOrder);

    TIFFSetErrorHandler(TIFFError);
	
	// Open the output image
	if((output = TIFFOpenW(mergedFileName, "w")) == NULL){
		AfxMessageBox(_T("Can't open output tiff file"));
		return;
	}

	// The outgoing image has many frames
	TIFFSetField(output, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE);
	
    uint32 directoryNumber = 0;
	uint32 directoryCount = fileNameArray.size();

	// Set the range of the progress control
	mergeProgress->SetRange(0, directoryCount);
	mergeProgress->SetStep(1);
	mergeProgress->SetPos(0);

    std::list<CString>::iterator iter;
	for (iter = fileNameArray.begin(); iter != fileNameArray.end(); iter++) {
        uint16 spp;
        CString fileName = (*iter);

        // Step in the progress control
        mergeProgress->StepIt();
        // Update the list box
        mergedFileNames->AddString((LPCTSTR)(_T("Merged file: ") + fileName));
        // Set the focus the last added item
        mergedFileNames->SetTopIndex(mergedFileNames->GetCount() - 1);
		mergedFileNames->UpdateWindow();
		m_page1.PostMessageW(WM_UPDATE_USER, 0, 0);
        TIFF* image;
        if (IsTiffFile(fileName) &&
            (image = TIFFOpenW(fileName, "r")) != NULL) {   // Merge tiff images
		    TIFFGetField(image, TIFFTAG_SAMPLESPERPIXEL, &spp);
		    if (spp == 1) {
                MergeNonColorImage(image, output, directoryNumber, directoryCount);
		    } else if (spp == 3) {
			    MergeColorImage(image, output, directoryNumber, directoryCount);
		    }

		    TIFFClose(image);
        } else {                                            // Merge jpeg images
            mergeJpegFile(fileName, output, directoryNumber, directoryCount);
        }
	}
	TIFFClose(output);
    mergedFileNames->AddString(_T("Generate file: ") + mergedFileName);
    mergedFileNames->SetTopIndex(mergedFileNames->GetCount() - 1);
	mergedFileNames->UpdateWindow();
	m_page1.PostMessageW(WM_UPDATE_USER, 0, 0);
}

bool containTiffOrJpegDirect(const CString& szPath)
{
    if (szPath.IsEmpty())
        return false;

    CFileFind finder;
	CString strWildCard = szPath;
    if (strWildCard.Right(1) != _T("\\")) {
        strWildCard += _T("\\");
    }
    strWildCard += _T("*");

	if (finder.FindFile(strWildCard)) {
		BOOL bWorking = TRUE;
		while (bWorking) {			
			bWorking = finder.FindNextFileW();
            if (finder.IsDots() || finder.IsDirectory()) {
				continue;
            }

            CString fileName = finder.GetFileName();
            if (IsTiffFile(fileName) ||
                IsJpegFile(fileName)) {
                finder.Close();
                return true;
            }
		}
		finder.Close();
	}
    return false;
}

bool IsTiffFile(const CString &fileName)
{
    int index = fileName.ReverseFind(_T('.'));
    CString suffix = fileName.Mid(index + 1);
    if (!suffix.CompareNoCase(_T("tiff")) || !suffix.CompareNoCase(_T("tif"))) {
        return true;
    }
    return false;
}

bool IsJpegFile(const CString& fileName)
{
    int index = fileName.ReverseFind(_T('.'));
    CString suffix = fileName.Mid(index + 1);
    if (!suffix.CompareNoCase(_T("jpg")) || 
        !suffix.CompareNoCase(_T("jpeg")) ||
        !suffix.CompareNoCase(_T("jpe")) ||
        !suffix.CompareNoCase(_T("jif")) ||
        !suffix.CompareNoCase(_T("jfif")) ||
        !suffix.CompareNoCase(_T("jfi"))
        ) {
        return true;
    }
    return false;
}

void TIFFError(const char* /*module*/, const char* fmt, va_list argptr)
{
    char msg[1024];
    vsprintf(msg, fmt, argptr);

    TRACE("TIFFError: %s\n", msg);
}

void JPEGError(j_common_ptr cinfo)
{
    TRACE("JPEGError: error happened\n");
}

void MergeNonColorImage(TIFF* input, TIFF* output, uint32& directoryNumber, uint32 directoryCount)
{
    uint32 width, height;	
	float xres, yres;
	uint16 resUnit, bpp, spp, compression;
	char *makeName = 0;
	char *softwareName = 0;
    char *raster;
	int stripMax, stripCount;
	unsigned long bufferSize, imageOffset, result;
	tsize_t stripSize;

	stripSize = TIFFStripSize(input);
	stripMax = TIFFNumberOfStrips(input);
	bufferSize = stripMax * stripSize;
	raster = new char[bufferSize];

    TIFFGetField(input, TIFFTAG_IMAGEWIDTH, &width);
	TIFFGetField(input, TIFFTAG_IMAGELENGTH, &height);
	TIFFGetField(input, TIFFTAG_XRESOLUTION, &xres);
	TIFFGetField(input, TIFFTAG_YRESOLUTION, &yres);
	TIFFGetField(input, TIFFTAG_RESOLUTIONUNIT, &resUnit);
	TIFFGetField(input, TIFFTAG_BITSPERSAMPLE, &bpp);
	TIFFGetField(input, TIFFTAG_SAMPLESPERPIXEL, &spp);
	TIFFGetField(input, TIFFTAG_COMPRESSION, &compression);
	
    bool errorOccurred = false;
    imageOffset = 0;
	for (stripCount = 0; stripCount < stripMax; stripCount++) {
		if ((result = TIFFReadEncodedStrip(input, stripCount, raster + imageOffset, stripSize)) == 0 ) {
            errorOccurred = true;
            break;
		}
		imageOffset += result;
	}
    
    if (errorOccurred) {
        delete[] raster;
        TIFFClose(input);
        return;
    }

	TIFFSetField(output, TIFFTAG_PAGENUMBER, directoryNumber++, directoryCount);

	// Write the tiff tags to the corresponding directory
	TIFFSetField(output, TIFFTAG_IMAGEWIDTH, width);
	TIFFSetField(output, TIFFTAG_IMAGELENGTH, height);
	TIFFSetField(output, TIFFTAG_COMPRESSION, compression);
	TIFFSetField(output, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);

	TIFFSetField(output, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE);
	TIFFSetField(output, TIFFTAG_BITSPERSAMPLE, bpp);
	TIFFSetField(output, TIFFTAG_SAMPLESPERPIXEL, spp);

	TIFFSetField(output, TIFFTAG_XRESOLUTION, xres);
	TIFFSetField(output, TIFFTAG_YRESOLUTION, yres);
	TIFFSetField(output, TIFFTAG_RESOLUTIONUNIT, resUnit);

	if (TIFFGetField(input, TIFFTAG_MAKE, &makeName)) {
		TIFFSetField(output, TIFFTAG_MAKE, makeName);
	}
	
	if (TIFFGetField(input, TIFFTAG_SOFTWARE, &softwareName)) {
		TIFFSetField(output, TIFFTAG_SOFTWARE, softwareName);
	}

	TIFFWriteEncodedStrip(output, 0, raster, bufferSize);
	TIFFWriteDirectory(output);

	delete[] raster;
}

void MergeColorImage(TIFF* input, TIFF* output, uint32& directoryNumber, uint32 directoryCount)
{
    uint32 width, height, imagesize;	
    uint32 *content;
	uint32 e = 0, c = 0;
    char *raster;
	float xres, yres;
	uint16 resUnit, bpp, spp, compression;
	char *makeName = 0;
	char *softwareName = 0;

    TIFFGetField(input, TIFFTAG_IMAGEWIDTH, &width);
	TIFFGetField(input, TIFFTAG_IMAGELENGTH, &height);
	TIFFGetField(input, TIFFTAG_XRESOLUTION, &xres);
	TIFFGetField(input, TIFFTAG_YRESOLUTION, &yres);
	TIFFGetField(input, TIFFTAG_RESOLUTIONUNIT, &resUnit);
	TIFFGetField(input, TIFFTAG_BITSPERSAMPLE, &bpp);
	TIFFGetField(input, TIFFTAG_SAMPLESPERPIXEL, &spp);
	TIFFGetField(input, TIFFTAG_COMPRESSION, &compression);
    
	imagesize = width * height + 1;
	raster = new char[width * height * 3];
	content = new uint32[imagesize];

	TIFFSetField(output, TIFFTAG_PAGENUMBER, directoryNumber++, directoryCount);

	// Write the tiff tags to the corresponding directory
	TIFFSetField(output, TIFFTAG_IMAGEWIDTH, width);
	TIFFSetField(output, TIFFTAG_IMAGELENGTH, height);
	
	TIFFSetField(output, TIFFTAG_COMPRESSION, compression);
	TIFFSetField(output, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
	TIFFSetField(output, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
	TIFFSetField(output, TIFFTAG_BITSPERSAMPLE, bpp);
	TIFFSetField(output, TIFFTAG_SAMPLESPERPIXEL, spp);

	TIFFSetField(output, TIFFTAG_XRESOLUTION, xres);
	TIFFSetField(output, TIFFTAG_YRESOLUTION, yres);
	TIFFSetField(output, TIFFTAG_RESOLUTIONUNIT, resUnit);

	if (TIFFGetField(input, TIFFTAG_MAKE, &makeName)) {
		TIFFSetField(output, TIFFTAG_MAKE, makeName);
	}
	
	if (TIFFGetField(input, TIFFTAG_SOFTWARE, &softwareName)) {
		TIFFSetField(output, TIFFTAG_SOFTWARE, softwareName);
	}

	if ((TIFFReadRGBAImage(input, width, height, content)) != 0) {
		for (e = 0; e < height; e++) {
			for (c = 0; c < width; c++) {
				raster[(e * width  + c) * 3 + 0] = TIFFGetR(content[(height - e - 1) * width + c]);
				raster[(e * width  + c) * 3 + 1] = TIFFGetG(content[(height - e - 1) * width + c]);
				raster[(e * width  + c) * 3 + 2] = TIFFGetB(content[(height - e - 1) * width + c]);
			}
		}						
	}
	
    TIFFWriteEncodedStrip(output, 0, raster, width * height * 3);
	    
	TIFFWriteDirectory(output);		
	
	delete[] content;
	delete[] raster;
}

void RecursiveRemoveDirectory(const CString& szPath)
{
    CFileFind ff;
	CString path = szPath;

	if (path.Right(1) != _T("\\")) {
		path += _T("\\");
	}

	path += _T("*.*");

	BOOL res = ff.FindFile(path);

	while (res) {
		res = ff.FindNextFile();

		if (!ff.IsDots() && !ff.IsDirectory()) {
			DeleteFile(ff.GetFilePath());
		} else if (ff.IsDirectory() && !ff.IsDots()) {
			path = ff.GetFilePath();
			RecursiveRemoveDirectory(path);
            if (!::RemoveDirectory(path)) {
                Log(_T("Remove directory %ws failed\n"), path);
            } else {
                Log(_T("Remove directory %ws succeeded\n"), path);
            }
		}        
	}
    ff.Close();
    
    if (!::RemoveDirectory(szPath)) {
        Log(_T("Remove directory %ws failed\n"), szPath);
    } else {
        Log(_T("Remove directory %ws succeeded\n"), szPath);
    }
}

void mergeJpegFile(const CString& szPath, TIFF* output, uint32& directoryNumber, uint32 directoryCount)
{
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    
    // More stuff
    // Source file
    FILE *inFile;
#ifdef _DEBUG
	setlocale(LC_ALL, "chs");
	int reqSize = wcstombs(NULL, szPath, 0);
	char *fileName = new char[reqSize + 1];
    wcstombs(fileName, (LPTSTR)(LPCTSTR)szPath, reqSize);
	setlocale(LC_ALL, "C");
	fileName[reqSize] = '\0';
#endif
    if((inFile = _wfopen(szPath, _T("rb"))) == NULL) {
        return;
    }

	// Got the exif information
	ExifData* ed = getExifInfo(szPath);

	
    cinfo.err = jpeg_std_error(&jerr);
    jerr.error_exit = JPEGError;
    jpeg_create_decompress(&cinfo);
    jpeg_stdio_src(&cinfo, inFile);

    (void) jpeg_read_header(&cinfo, TRUE);
    (void) jpeg_start_decompress(&cinfo);
    if (cinfo.output_components == 1) { // Grayscale image
#ifdef _DEBUG
		TRACE("Merged gray jpeg file: %s\n", fileName);
#endif
        mergeGrayJpegFile(cinfo, ed, output, directoryNumber, directoryCount);
    } else if (cinfo.output_components == 3) { // Color image
#ifdef _DEBUG
		TRACE("Merged color jpeg file: %s\n", fileName);
#endif
        mergeColorJpegFile(cinfo, ed, output, directoryNumber, directoryCount);
    }
#ifdef _DEBUG
	delete[] fileName;
#endif
    exif_data_unref(ed);
    fclose(inFile);    
}

void mergeGrayJpegFile(struct jpeg_decompress_struct &cinfo, ExifData *ed, TIFF *output, uint32& directoryNumber, uint32 directoryCount)
{
    int row_stride = cinfo.output_width * cinfo.output_components;
    int width = cinfo.output_width;
    int height = cinfo.output_height;
    int components = cinfo.output_components;
    // Allocation for a B/W picture
    unsigned char* grayJpegPic;
    grayJpegPic = new unsigned char[width * height * components];
    while (cinfo.output_scanline < cinfo.output_height) {                      // read a jpeg picture file  
        unsigned char* scanline = grayJpegPic + cinfo.output_scanline * row_stride;
        (void) jpeg_read_scanlines(&cinfo, &scanline, 1);
    }
    (void) jpeg_finish_decompress(&cinfo);    

    TIFFSetField(output, TIFFTAG_PAGENUMBER, directoryNumber++, directoryCount);

    TIFFSetField(output, TIFFTAG_IMAGEWIDTH, width);             // Set fields of a TIFF file  
    TIFFSetField(output, TIFFTAG_IMAGELENGTH, height);
    TIFFSetField(output, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);          // set storage organization to 1

	TIFFSetField(output, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);
    TIFFSetField(output, TIFFTAG_COMPRESSION, COMPRESSION_LZW);
    
    setResolutionInfo(cinfo, ed, output);

	TIFFSetField(output, TIFFTAG_BITSPERSAMPLE, 8);                           // 8 bit pier pixel     
    TIFFSetField(output, TIFFTAG_SAMPLESPERPIXEL, 1);                          // 1 channel/sample/component per pixel 

    TIFFWriteEncodedStrip(output, 0, grayJpegPic, width * height * components);
	TIFFWriteDirectory(output);
    delete[] grayJpegPic;
    jpeg_destroy_decompress(&cinfo);
}

void mergeColorJpegFile(struct jpeg_decompress_struct &cinfo, ExifData* ed, TIFF *output, uint32& directoryNumber, uint32 directoryCount)
{
    int row_stride = cinfo.output_width * cinfo.output_components;
    char *colorJpegPic;
    int width = cinfo.output_width;
    int height = cinfo.output_height;
    int components = cinfo.output_components;

    // Allocation for a Color picture
    colorJpegPic = new char[width * height * components];

    while (cinfo.output_scanline < cinfo.output_height) {                      // read a jpeg picture file 
        char* scanline = colorJpegPic + cinfo.output_scanline * row_stride;
        (void) jpeg_read_scanlines(&cinfo, (JSAMPARRAY)&scanline, 1);
    }
    (void) jpeg_finish_decompress(&cinfo);
    

#ifdef _DEBUG
    Log(LOG_INFO, _T("Directory number: %d, Width: %d, Height: %d\n"), directoryNumber, width, height);
#endif
    TIFFSetField(output, TIFFTAG_PAGENUMBER, directoryNumber++, directoryCount);
    
    TIFFSetField(output, TIFFTAG_IMAGEWIDTH, width);             // Set fields of a TIFF file  
    TIFFSetField(output, TIFFTAG_IMAGELENGTH, height);

    TIFFSetField(output, TIFFTAG_COMPRESSION, COMPRESSION_LZW);
    TIFFSetField(output, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);          // set storage organization to 1
    TIFFSetField(output, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
    TIFFSetField(output, TIFFTAG_BITSPERSAMPLE, 8);                           // 8 bit pier pixel     
    TIFFSetField(output, TIFFTAG_SAMPLESPERPIXEL, 3);                          // 1 channel/sample/component per pixel 

    setResolutionInfo(cinfo, ed, output);   
    TIFFWriteEncodedStrip(output, 0, colorJpegPic, width * height * components);
	TIFFWriteDirectory(output); 
    delete[] colorJpegPic;
    jpeg_destroy_decompress(&cinfo);
}

ExifData* getExifInfo(const char* filePath)
{
	return exif_data_new_from_file(filePath);
}

ExifData* getExifInfo(const CString& szPath)
{
	setlocale(LC_ALL, "chs");
	int reqSize = wcstombs(NULL, szPath, 0);
	char *fileName = new char[reqSize + 1];
    wcstombs(fileName, (LPTSTR)(LPCTSTR)szPath, reqSize);
	setlocale(LC_ALL, "C");
	fileName[reqSize] = '\0';
	ExifData* ed = getExifInfo(fileName);

#ifdef _DEBUG
	getMakerInfo(NULL, ed);
#endif

	delete[] fileName;
	return ed;
}

bool getMakerInfo(char* maker, ExifData* ed)
{
    if (!ed) 
        return false;
	showTag(ed, EXIF_IFD_0, EXIF_TAG_ARTIST);
	showTag(ed, EXIF_IFD_0, EXIF_TAG_XP_AUTHOR);
	showTag(ed, EXIF_IFD_0, EXIF_TAG_COPYRIGHT);
	
	showTag(ed, EXIF_IFD_EXIF, EXIF_TAG_USER_COMMENT);
	showTag(ed, EXIF_IFD_0, EXIF_TAG_IMAGE_DESCRIPTION);
	showTag(ed, EXIF_IFD_1, EXIF_TAG_IMAGE_DESCRIPTION);
	showTag(ed, EXIF_IFD_0, EXIF_TAG_X_RESOLUTION);
    showTag(ed, EXIF_IFD_0, EXIF_TAG_Y_RESOLUTION);
    showTag(ed, EXIF_IFD_0, EXIF_TAG_RESOLUTION_UNIT);
	showTag(ed, EXIF_IFD_0, EXIF_TAG_SOFTWARE);

	return true;
}

void showTag(ExifData *d, ExifIfd ifd, ExifTag tag)
{
    if (!d)
        return;
	/* See if the tag exists*/
	ExifEntry *entry = exif_content_get_entry(d->ifd[ifd], tag);
	if (entry) {
		char buf[1024] = {0};

		/* Get the contents of the tag in human-readable form*/
		exif_entry_get_value(entry, buf, sizeof(buf));

		TRACE("TAG: %s: %s\n", exif_tag_get_name_in_ifd(tag, ifd), buf);
	}
}

string getTagValue(ExifData *ed, ExifIfd ifd, ExifTag tag)
{
    /* See if the tag exists */
    string value;
    if (!ed) 
        return value;
	ExifEntry *entry = exif_content_get_entry(ed->ifd[ifd], tag);
	if (entry) {
		char buf[1024] = {0};

		/* Get the contents of the tag in human-readable form*/
		exif_entry_get_value(entry, buf, sizeof(buf));
       
        value.assign(buf, buf + strlen(buf));
	}
    getResolution(ed, ifd, tag);
    return value;
}

float getResolution(ExifData *ed, ExifIfd ifd, ExifTag tag)
{
    /* See if the tag exists */
    float resolution = -1.0f;
    if (!ed) 
        return resolution;
	ExifEntry *entry = exif_content_get_entry(ed->ifd[ifd], tag);
	if (entry) {
        ExifRational res = exif_get_rational(entry->data, exif_data_get_byte_order(ed));
        resolution = res.numerator / res.denominator * 1.0f;
	}
    return resolution;
}
uint16_t getResolutionUnit(ExifData *ed, ExifIfd ifd, ExifTag tag)
{
    /* See if the tag exists */
    uint16_t value = 0;
    if (!ed) 
        return value;
	ExifEntry *entry = exif_content_get_entry(ed->ifd[ifd], tag);
	if (entry) {
        value = exif_get_short(entry->data, exif_data_get_byte_order(ed));
	}
    return value;
}

void setResolutionInfo(const struct jpeg_decompress_struct &cinfo, ExifData *ed, TIFF *output)
{
    float v = getResolution(ed, EXIF_IFD_0, EXIF_TAG_X_RESOLUTION);  
    if (v == -1.0f) {      // No EXIF information, have JFIF information
        v = cinfo.X_density;
    }
#ifdef _DEBUG
    Log(LOG_INFO, _T("Xres: %f\n"), v);
#endif
    TIFFSetField(output, TIFFTAG_XRESOLUTION, v);

    v = getResolution(ed, EXIF_IFD_0, EXIF_TAG_Y_RESOLUTION);   
    if (v == -1.0f) {
        v = cinfo.Y_density;
    }
#ifdef _DEBUG
    Log(LOG_INFO, _T("Yres: %f\n"), v);
#endif
    TIFFSetField(output, TIFFTAG_YRESOLUTION, v);
    uint16 resUnit = getResolutionUnit(ed, EXIF_IFD_0, EXIF_TAG_RESOLUTION_UNIT);
    if ( resUnit == 0) {
        resUnit = cinfo.density_unit + 1;
    }
#ifdef _DEBUG
    Log(LOG_INFO, _T("ResUnit: %d\n"), resUnit);
#endif
    TIFFSetField(output, TIFFTAG_RESOLUTIONUNIT, resUnit);

    string value = getTagValue(ed, EXIF_IFD_0, EXIF_TAG_SOFTWARE);
    if (!value.empty()) {
        TIFFSetField(output, TIFFTAG_SOFTWARE, value.c_str());
#ifdef _DEBUG
    Log(LOG_INFO, "Software: %s\n", value.c_str());
#endif
    }
}
